package com.aim.game;

import java.util.LinkedList;

/**
 * Created with IntelliJ IDEA.
 * User: User
 * Date: 5/9/14
 * Time: 3:59 PM
 * To change this template use File | Settings | File Templates.
 */
public interface GameRule {

    public GameResult runOneGame(GameParam params, Player player);

    /**
     * This implementation not shift the time of next event when player perform any action.
     */

    public static class GameRuleBase implements GameRule{

        private static boolean debugMode = false;
        protected boolean actionPostponeNextEventTime;

        public GameRuleBase() {
            this(true);
        }

        public GameRuleBase(boolean actionPostponeNextEventTime) {
            this.actionPostponeNextEventTime = actionPostponeNextEventTime;
        }

        @Override
        public GameResult runOneGame(GameParam p, Player player) {
            GameResult result = new GameResult();
            result.gameLength = p.gameLength;
            result.scorePerformance = player.getScorePerformance();
            result.totalCost = 0;
            result.playerPolicy = player.getPolicyTime();
            MC mc = MC.values()[0];

            long freezeTime = -1;
            long time = 0;
            long nextEventTime = p.eventGenerator.nextTime();
            long timeFromLastEvent = 0;

            write(result, time, "start game",  mc, timeFromLastEvent, 0, nextEventTime, GameAction.NO_ACTION);
            while (time < p.gameLength) {
                // force action if player have to perform repair
                if (mc != MC.FIX){
                    if ((p.gameLength - time) <= p.costTable.getTime(mc)){
                        write(result, time, "force fix", mc, timeFromLastEvent, 0, nextEventTime, GameAction.REPAIR);
                        takeAction(p, result, mc, GameAction.REPAIR);
                        System.out.println("forceClose");
                        break;
                    }
                }

                timeFromLastEvent += p.timeInterval;
                time += p.timeInterval;

                // event occurred
                if (time >= nextEventTime) {
                    assert mc != MC.M3;

                    long generatedNextTime = p.eventGenerator.nextTime();
                    write(result, time, "event occur (" + generatedNextTime + ")",  mc, timeFromLastEvent, generatedNextTime, nextEventTime, GameAction.NO_ACTION);
                    nextEventTime = time + generatedNextTime;
                    timeFromLastEvent = 0;
                    mc = MC.next(mc);
                }

                if (time <= freezeTime) {

                } else {
                    GameAction takeAction = player.onTime(timeFromLastEvent, mc);

                    if (takeAction != GameAction.NO_ACTION){
                        if (takeAction == GameAction.MAINTENANCE){
                            long generatedNextTime = p.eventGenerator.nextTime();
                            write(result, time, "maintenance (" + generatedNextTime + ")",  mc, timeFromLastEvent, generatedNextTime, nextEventTime, GameAction.MAINTENANCE);
                            takeAction(p, result, mc, GameAction.MAINTENANCE);

                            nextEventTime = time + generatedNextTime;
                            timeFromLastEvent = 0;

                            freezeTime = time + p.costTable.getTime(MC.FIX);

                            if (isActionPostponeNextEventTime()) {
                                long costTime = p.costTable.getTime(MC.FIX);
                                nextEventTime += costTime;
                                timeFromLastEvent -= costTime;
                            }
                        }
                        if (takeAction == GameAction.REPAIR){
                            write(result, time, "repair",  mc, timeFromLastEvent, 0, nextEventTime, GameAction.REPAIR);
                            takeAction(p, result, mc, GameAction.REPAIR);
                            freezeTime = time + p.costTable.getTime(mc);
                            mc = MC.FIX;
                            if (isActionPostponeNextEventTime()) {
                                long costTime = p.costTable.getTime(mc);
                                nextEventTime += costTime;
                                timeFromLastEvent -= costTime;
                            }
                        }
                    }
                }
            }
            return result;
        }

        private void takeAction(GameParam p, GameResult result, MC mc, GameAction action){
            result.totalCost += p.costTable.getActionCost(action == GameAction.MAINTENANCE ? MC.FIX : mc);
            result.totalTimeCost += p.costTable.getTime(action == GameAction.MAINTENANCE ? MC.FIX : mc);
            switch (action){
                case MAINTENANCE: result.maintenanceCount++; break;
                case REPAIR: result.repairCount++;
            }
        }

        private static void write(GameResult result, long time, String freeText, MC mc,
                                  long timeFromLastEvent, long generatedNextTime, long nextEventTime, GameAction gameAction){
            if (!debugMode){
                return;
            }
            if (result.logs == null){
                result.logs = new LinkedList<String>();
                String header = "time\t"
                        + "mc\t"
                        + "freeText\t"
                        + "timeFromLastEvent\t"
                        + "nextEventTime\t"
                        + "generatedNextTime\t"
                        + "gameAction\t";
                result.logs.add(header);
            }
            String line = time + "\t"
                    + mc + "\t"
                    + freeText + "\t"
                    + timeFromLastEvent + "\t"
                    + nextEventTime + "\t"
                    + generatedNextTime + "\t"
                    + gameAction;
            result.logs.add(line);
        }

        public boolean isActionPostponeNextEventTime() {
            return actionPostponeNextEventTime;
        }

        public void setActionPostponeNextEventTime(boolean actionPostponeNextEventTime) {
            this.actionPostponeNextEventTime = actionPostponeNextEventTime;
        }
    }

}
